

#include <vulkan/vulkan.h>

#include "camera.hpp"
#include "VulkanUtils.h"
#include "VulkanglTFModel.hpp"
#include "VulkanTexture.hpp"
#include "VulkanDevice.hpp"

class GLTF_Render
{
private:
    enum PBRWorkflows{ PBR_WORKFLOW_METALLIC_ROUGHNESS = 0, PBR_WORKFLOW_SPECULAR_GLOSINESS = 1 };

    struct UniformBufferSet {
        Buffer scene;
        // Buffer skybox;
        // Buffer params;
    };

    struct UBOMatrices {
        glm::mat4 projection;
        glm::mat4 model;
        glm::mat4 view;
        glm::vec3 camPos;
    } shaderValuesScene;

    struct shaderValuesParams {
        glm::vec4 lightDir;
        float exposure = 4.5f;
        float gamma = 2.2f;
        float prefilteredCubeMipLevels;
        float scaleIBLAmbient = 1.0f;
        float debugViewInputs = 0;
        float debugViewEquation = 0;
    } shaderValuesParams;

    struct PushConstBlockMaterial {
        glm::vec4 baseColorFactor;
        glm::vec4 emissiveFactor;
        glm::vec4 diffuseFactor;
        glm::vec4 specularFactor;
        float workflow;
        int colorTextureSet;
        int PhysicalDescriptorTextureSet;
        int normalTextureSet;
        int occlusionTextureSet;
        int emissiveTextureSet;
        float metallicFactor;
        float roughnessFactor;
        float alphaMask;
        float alphaMaskCutoff;
    } pushConstBlockMaterial;

    struct Pipelines {
        // VkPipeline skybox;
        VkPipeline pbr{VK_NULL_HANDLE};
        VkPipeline pbrAlphaBlend{VK_NULL_HANDLE};
    } pipelines;

    struct DescriptorSetLayouts {
        VkDescriptorSetLayout scene{VK_NULL_HANDLE};
        VkDescriptorSetLayout material{VK_NULL_HANDLE};
        VkDescriptorSetLayout node{VK_NULL_HANDLE};
    } descriptorSetLayouts;

    struct LightSource {
        glm::vec3 color = glm::vec3(1.0f);
        glm::vec3 rotation = glm::vec3(75.0f, 40.0f, 0.0f);
    } lightSource;

    struct DescriptorSets {
        VkDescriptorSet scene;
        // VkDescriptorSet skybox;
    };
    std::vector<DescriptorSets>     descriptorSets;
    std::vector<UniformBufferSet>   uniformBuffers;
    std::vector<Buffer> *uniformBufferParams;

    bool                loaded{false};
    vkglTF::Model       scene;
    Camera             *camera;
    vks::VulkanDevice  *vulkanDevice;
    Textures           *textures;

    VkDevice device;
    VkQueue queue;
    VkRenderPass renderPass;
    VkPipelineLayout pipelineLayout{VK_NULL_HANDLE};
    VkDescriptorPool descriptorPool{VK_NULL_HANDLE};
    VkPipelineCache  pipelineCache;
    VkSampleCountFlagBits multiSampleCount;

    uint32_t    frameBufferCount = 3;
    int32_t     animationIndex   = 0;
    float       animationTimer   = 0.0f;
    bool        animate          = true;

    void prepareUniformBuffers();
    void setupNodeDescriptorSet(vkglTF::Node *node);
    void renderNode(VkCommandBuffer currentCB, vkglTF::Node *node, uint32_t cbIndex, vkglTF::Material::AlphaMode alphaMode);

public:
    GLTF_Render(vks::VulkanDevice *vulkanDevice, uint32_t frameBufferCount, VkRenderPass renderPass,
        VkQueue queue, VkPipelineCache pipelineCache, VkSampleCountFlagBits multiSampleCount, Textures *textures,
        Camera *camera, std::vector<Buffer> *uniformBufferParams);
    ~GLTF_Render();
    void updateUniformBuffers(uint32_t cbIndex);
    bool load(std::string uri);
    void setupDescriptors();
    void preparePipelines();
    void recordCommandBuffers(VkCommandBuffer currentCB, uint32_t frameIndex);
    void render(float time);
    // void updateParams();
};